﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using EnvDTE80;
using Microsoft.Win32;

namespace SearchInFiles
{
    internal static class Extensions
    {
        private const string DOTS = "...";

        public static IEnumerable<int> AllIndexesOf(this string str, string substr, bool ignoreCase = false)
        {
            if (string.IsNullOrWhiteSpace(str) ||
                string.IsNullOrWhiteSpace(substr))
            {
                throw new ArgumentException("String or substring is not specified.");
            }

            var indexes = new List<int>();
            var index = 0;

            while (
                (index =
                 str.IndexOf(substr, index, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) !=
                -1)
            {
                indexes.Add(index++);
            }

            return indexes.ToArray();
        }

        public static T[] NullIfEmpty<T>(this T[] arr)
        {
            return !arr.Any() ? null : arr;
        }

        public static IEnumerable<T> NullIfEmpty<T>(this IEnumerable<T> e)
        {
            var nullIfEmpty = e as IList<T> ?? e.ToList();
            return !nullIfEmpty.Any() ? null : nullIfEmpty;
        }

        public enum TrimType
        {
            NoTrimNoCollapse,
            NoTrim,
            TrimStart,
            TrimEnd
        }

        public static string Collapse(this string s, int maxLength, TrimType trimType)
        {
            switch (trimType)
            {
                case TrimType.TrimStart:
                    s = s.TrimStart();
                    break;
                case TrimType.TrimEnd:
                    s = s.TrimEnd();
                    break;
            }
            if (s.Length < maxLength)
                return s;
            switch (trimType)
            {
                case TrimType.TrimStart:
                    return DOTS + s.Substring(s.Length - maxLength);
                case TrimType.NoTrim:
                    var half = maxLength / 2;
                    return s.Substring(0, half) + DOTS + s.Substring(s.Length - half);
                case TrimType.NoTrimNoCollapse:
                    return s;
                case TrimType.TrimEnd:
                    return s.Substring(0, maxLength) + DOTS;
            }
            return s;
        }
    }

    internal static class Utilities
    {
        private const string VS_VIEW_KIND_TEXT_VIEW = "{7651A703-06E5-11D1-8EBD-00A0C90F26EA}";

        private static readonly string[] VisualStudioVersions =
            {
                "11.0", "10.0", "9.0", "8.0"
            };

        private static readonly HashSet<string> TextExtensions = new HashSet<string>
            {
                ".txt",
                ".guide",
                ".rtf",
                ".odt",
                ".sxw",
                ".tex",
                ".texi",
                ".wpd",
                ".docm",
                ".lwp",
                ".gz",
                ".htm",
                ".xml",
                ".xht",
                ".maff",
                ".mhtml",
                ".cshtmls",
                ".config",
                ".xaml",
                ".asp",
                ".aspx",
                ".bml",
                ".cfm",
                ".cgi",
                ".ihtml",
                ".jsp",
                ".java",
                ".lassoapp",
                ".pl",
                ".phtml",
                ".shtm",
                ".asm",
                ".h",
                ".hpp",
                ".pas",
                ".bas",
                ".bbc",
                ".js",
                ".cs",
                ".ml",
                ".pm",
                ".phtml",
                ".py",
                ".rbw",
                ".s7i",
                ".sha",
                ".vsm",
                ".out",
                ".bat",
                ".cmd",
                ".reg"
            };

        private static string Format(string format, object[] args)
        {
            return format != null ? String.Format(format, args) : null;
        }

        /// <summary>
        /// Подставляет указанные аргументы в одну из строк форматирования для получения правильного множественного числа.
        /// </summary>
        /// <param name="number">Целочисленное значение, определяющее какую из строк форматирования использовать.</param>
        /// <param name="oneFormat">Строка форматирования, например "Я хочу {0} печеньку", используемая для форматирования чисел, таких как 1, 21, 101 и т.п.</param>
        /// <param name="fewFormat">Строка форматирования, например "Я хочу {0} печеньки", используемая для форматирования чисел, таких как 2, 4, 42 и т.п.</param>
        /// <param name="manyFormat">Строка форматирования, например "Я хочу {0} печенек", используемая для форматирования чисел, таких как 5, 11, 20 и т.п.</param>
        /// <param name="args">Аргументы форматирования.</param>
        public static string FormatPlural(int number, string oneFormat, string fewFormat, string manyFormat,
                                          params object[] args)
        {
            if (number == 0)
            {
                return Format(manyFormat, args);
            }

            var mod10 = number % 10;
            if (mod10 == 0 || mod10 == 5 || mod10 == 6 || mod10 == 7 || mod10 == 8 || mod10 == 9)
            {
                return Format(manyFormat, args);
            }

            var mod100 = number % 100;
            if (mod100 == 11 || mod100 == 12 || mod100 == 13 || mod100 == 14)
            {
                return Format(manyFormat, args);
            }

            return Format(mod10 == 1 ? oneFormat : fewFormat, args);
        }

        /// <summary>
        /// Return registered application by file's extension
        /// </summary>
        /// <param name="fileExtension">File extension</param>
        /// <param name="appName">Returns application name if any</param>
        /// <returns>True if the default application for this file type was found</returns>
        /// <remarks>This function is Windows XP and Vista compatible</remarks>
        public static bool GetRegisteredApplication(string fileExtension, out string appName)
        {
            appName = null;
            try
            {
                // Get Programmatic Identifier for this extension
                string strProgId;
                RegistryKey oHkcr;
                try
                {
                    oHkcr = Registry.ClassesRoot;
                    var oProgId = oHkcr.OpenSubKey(fileExtension);
                    strProgId = oProgId.GetValue(null).ToString();
                    oProgId.Close();
                }
                catch
                {
                    // No ProgID, return false
                    return false;
                }
                try
                {
                    var oOpenCmd = oHkcr.OpenSubKey(strProgId + "\\shell\\open\\command");
                    appName = oOpenCmd.GetValue(null).ToString();
                    oOpenCmd.Close();
                }
                catch
                {
                    // Missing default application
                    return false;
                }
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// Открыть файл в ОТКРЫТОЙ вижуал студии и перейти на линию
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="lineNumber"></param>
        /// <param name="visualStudioPreferredVersion"></param>
        /// <returns></returns>
        public static bool OpenWithVisualStudio(string fileName, int lineNumber,
                                                string visualStudioPreferredVersion = "11.0")
        {
            var versions = VisualStudioVersions.ToList();
            if (versions.IndexOf(visualStudioPreferredVersion) > 0)
            {
                versions.Remove(visualStudioPreferredVersion);
                versions.Insert(0, visualStudioPreferredVersion);
            }
            foreach (var version in versions)
            {
                DTE2 dte2;
                try
                {
                    dte2 = (DTE2) System.Runtime.InteropServices.Marshal.GetActiveObject(string.Format("VisualStudio.DTE.{0}", version));
                    dte2.MainWindow.Activate();
                    dte2.ItemOperations.OpenFile(fileName, VS_VIEW_KIND_TEXT_VIEW);
                }
                catch (Exception ex)
                {
                    //Если вообще не получилось открыть файл в вижуал студии, то пробуем с другой версией
                    continue;
                }
                try
                {
                    ((EnvDTE.TextSelection) dte2.ActiveDocument.Selection).GotoLine(lineNumber, true);
                }
                catch (Exception ex)
                {
                    //Если открыть получилось, но не получилось перейти на позицию, то считаем действие выполненным
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Detect if extension is text
        /// </summary>
        public static bool IsText(string extension)
        {
            return TextExtensions.Contains(extension);
        }
    }

    internal static class AnimationExtensions
    {
        internal enum TransitionSpeed
        {
            Instant = 0,
            Fast = 100,
            Normal = 200,
            Slow = 500
        }

        /// <summary>
        /// Toggles the opacity of a control.
        /// </summary>
        /// <param name="control">The control.</param>
        /// <param name="speed">The speed.</param>
        internal static void HideControl(this FrameworkElement control, TransitionSpeed speed = TransitionSpeed.Normal)
        {
            control.ToggleControl(true, speed);
        }

        /// <summary>
        /// Toggles the opacity of a control.
        /// </summary>
        /// <param name="control">The control.</param>
        /// <param name="speed">The speed.</param>
        internal static void ShowControl(this FrameworkElement control, TransitionSpeed speed = TransitionSpeed.Normal)
        {
            control.ToggleControl( false,speed);
        }

        internal static void ToggleControl(this FrameworkElement control, bool hide, TransitionSpeed speed = TransitionSpeed.Normal)
        {
            if ((hide && control.Opacity < 1) || (!hide && control.Opacity > 0))
            {
                return;
            }
            var from = hide ? 1 : 0;
            var to = from ^ 1;
            var storyboard = new Storyboard();
            var duration = new TimeSpan(0, 0, 0, 0, (int) speed);
            var animation = new DoubleAnimation {From = from, To = to, Duration = new Duration(duration)};
            storyboard.Completed += delegate
                {
                    control.Visibility = hide ? Visibility.Hidden : Visibility.Visible;
                };
            Storyboard.SetTargetName(animation, control.Name);
            Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity", 0));
            storyboard.Children.Add(animation);
            storyboard.Begin(control);
        }

        internal static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
        {
            if (depObj == null) yield break;
            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                var child = VisualTreeHelper.GetChild(depObj, i);
                if (child is T)
                {
                    yield return (T)child;
                }

                foreach (var childOfChild in FindVisualChildren<T>(child))
                {
                    yield return childOfChild;
                }
            }
        }
    }
}
